﻿#include <graphics.h>
#include <conio.h>
#include <stdio.h>
#include <math.h>
#include <vector>
#include <time.h>
#include <iostream>
#include <algorithm>
#include <corecrt_wstdio.h>
#pragma comment(lib,"Winmm.lib")

using namespace std;

#define  WIDTH 1000
#define  HEIGHT 700
#define  RADIUS 25
#define  COLOR_NUM 5
COLORREF  colors[COLOR_NUM] = { RED,BLUE,GREEN,YELLOW,MAGENTA };

class Point
{
public:
	float x;
	float y;
	Point()
	{
	}
	Point(float ix, float iy)
	{
		x = ix;
		y = iy;
	}
};

class Path
{
public:
	vector<Point> key_points; //记录轨迹上的一些关键点。
	float sample_interval; //对连接特征点的线进行采样。
	vector<Point> all_points;

	void get_all_points() //以采样间隔进行采样，得到所有的采样点。
	{
		int i;
		//对连接关键点的线进行遍历。
		for (i = 0; i < key_points.size() - 1; i++)
		{
			float xd = key_points[i + 1].x - key_points[i].x;
			float yd = key_points[i + 1].y - key_points[i].y;
			float length = sqrt(xd * xd + yd * yd);

			int num = length / sample_interval; //这一段线要被采样的个数。
			for (int j = 0; j < num; j++)
			{
				float x_sample = key_points[i].x + j * xd / num;
				float y_sample = key_points[i].y + j * yd / num;
				all_points.push_back(Point(x_sample, y_sample)); //添加所有采样点。
			}
		}
		all_points.push_back(Point(key_points[i].x, key_points[i].y));
	}


	void draw()
	{
		setlinecolor(BLACK);
		setfillcolor(BLACK);
		//连接采样点画线。
		for (int i = 0; i < key_points.size() - 1; i++)
		{
			line(key_points[i].x, key_points[i].y, key_points[i + 1].x, key_points[i + 1].y);
		}
		//在采样点处画圆。
		for (int i = 0; i < all_points.size(); i++)
		{
			fillcircle(all_points[i].x, all_points[i].y, 3);
		}

	}


	~Path()  //析构函数。
	{
		key_points.clear();
		all_points.clear();
	}
};

class Ball
{
public:
	Ball()
	{
	}
	Point center;
	float radius;
	int color_Id;
	int index_in_path;
	int direction;

	void draw()
	{
		setlinecolor(colors[color_Id]);
		setfillcolor(colors[color_Id]);
		fillcircle(center.x, center.y, radius);
	}


	void move_to_index_in_path(Path path)
	{
		//让球移动到Path的all_points的index_in_path序号位置。
		center = path.all_points[index_in_path];
	}


	void initiate(Path path)
	{
		radius = RADIUS;
		index_in_path = 0;
		direction = 0;
		move_to_index_in_path(path);
		color_Id = rand() % COLOR_NUM;
	}


	void change_index_by_direction(Path path)
	{
		if (direction == 1 && index_in_path + 1 < path.all_points.size())
		{
			index_in_path++;
		}
		else if (direction == -1 && index_in_path - 1 >= 0)
		{
			index_in_path--;
		}
	}
};

class Cannon
{
public:
	IMAGE im;
	IMAGE im_rotate;
	float x;
	float y;
	Ball ball;
	float angle;

	void draw()
	{
		rotateimage(&im_rotate, &im, angle, RGB(160, 211, 255), false, false);
		putimage(x - im.getwidth() / 2, y - im.getheight() / 2, &im_rotate);
		ball.draw();
	}

	void set_ball_position()
	{
		ball.center.x = x + 100 * cos(angle);
		ball.center.y = y + 100 * sin(angle);
	}


	void update_with_mouse_move(int mx, int my)
	{
		float xs = mx - x;
		float ys = my - y;
		float length = sqrt(xs * xs + ys * ys);
		if (length > 4)
		{
			angle = atan2(-ys, xs);

			ball.center.x = x + 100 * xs / length;
			ball.center.y = y + 100 * ys / length;
		}
	}

	void update_with_right_button_down()
	{
		//更改炮台要发射的小球颜色。
		ball.color_Id += 1;
		if (ball.color_Id == COLOR_NUM)
		{
			ball.color_Id = 0;
		}
	}
};

Path path;
vector <Ball> balls;
IMAGE im_bk;
IMAGE im_role;
IMAGE im_house;
Cannon cannon;
int game_status = 0;

void startup();
void show();
void update_without_input();
void update_with_input();
float Distance(float x1, float y1, float x2, float y2);
void sleep(DWORD ms);
void play_music_once(TCHAR file_name[80]);
int erase_same_color_balls(int i, Ball fire_ball, Path& path, vector <Ball>& balls);
void game_over();

int main()
{
	startup();
	while (!(_kbhit() && _getch() == 'q'))
	{
		show();
		update_without_input();
		update_with_input();
	}
	game_over();
	return 0;
}


void startup()
{
	mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL);
	mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);

	srand(time(0));
	initgraph(WIDTH, HEIGHT);
	setbkcolor(WHITE);
	cleardevice();
	loadimage(&im_bk, _T("bk.jpg"));
	loadimage(&im_role, _T("role.jpg"));
	loadimage(&im_house, _T("house.jpg"));

	//为轨迹类添加一些关键点。
	path.key_points.push_back(Point(50, 300));
	path.key_points.push_back(Point(50, 600));
	path.key_points.push_back(Point(100, 650));
	path.key_points.push_back(Point(700, 650));
	path.key_points.push_back(Point(700, 550));
	path.key_points.push_back(Point(250, 550));
	path.key_points.push_back(Point(200, 500));
	path.key_points.push_back(Point(200, 200));
	path.key_points.push_back(Point(250, 150));
	path.key_points.push_back(Point(800, 150));
	path.key_points.push_back(Point(850, 200));
	path.key_points.push_back(Point(850, 650));
	path.key_points.push_back(Point(950, 650));
	path.key_points.push_back(Point(950, 100));
	path.key_points.push_back(Point(900, 50));
	path.key_points.push_back(Point(150, 50));

	path.sample_interval = RADIUS / 5; //设置采样间隔，需被RADIUS整除。	
	path.get_all_points();

	cannon.im = im_role;
	cannon.angle = 0;
	cannon.x = 500;
	cannon.y = 350;
	cannon.ball.radius = RADIUS;
	cannon.ball.color_Id = rand() % COLOR_NUM;
	cannon.set_ball_position();

	//添加小球。
	for (int i = 0; i < 15; i++)
	{
		Ball ball;
		ball.initiate(path);
		balls.push_back(ball);
	}

	BeginBatchDraw();
}

void show()
{
	cleardevice();

	putimage(0, 0, &im_bk);
	putimage(30, 10, &im_house);
	cannon.draw();
	path.draw();

	for (int i = 0; i < balls.size(); i++)
	{
		balls[i].draw();
	}

	setbkmode(TRANSPARENT);
	settextcolor(RGB(255, 0, 0));
	settextstyle(60, 0, _T("楷体"));
	if (game_status == 1)
	{
		outtextxy(WIDTH * 0.35, HEIGHT * 0.35, _T("游戏胜利 :)"));
	}
	else if (game_status == -1)
	{
		outtextxy(WIDTH * 0.35, HEIGHT * 0.35, _T("游戏失败 :("));
	}

	FlushBatchDraw();
	Sleep(1);
}


void update_without_input()
{
	static clock_t start = clock();
	clock_t now = clock();

	int now_second = (int(now - start) / CLOCKS_PER_SEC);

	//100秒内，时间每过20秒钟，新增一批小球。
	if (now_second % 20 == 0 && now_second <= 100 && game_status == 0)
	{
		Ball ball;
		ball.initiate(path);
		balls.push_back(ball);
	}
	if (balls.size() == 0)
	{
		if (now_second > 100)//时间到了，游戏胜利。
		{
			game_status = 1;
		}
		return; //没有到截止时间，等到到时间后产生新的小球
	}

	//第一个球跑到终点则游戏失败。
	if (balls[0].index_in_path >= path.all_points.size() - 1)
	{
		game_status = -1;
		return;
	}

	int i;
	for (i = 0; i < balls.size(); i++)
	{
		balls[i].direction = 0;
	}

	i = balls.size() - 1;
	balls[i].direction = 1;

	while (i > 0)
	{
		// 如果前后两个小球正好相切，且前一个小球的方向也是向前，则对前一个小球的indexInPath进行规则化，确保正好相切。
		if (balls[i - 1].index_in_path - balls[i].index_in_path <= 2 * RADIUS / path.sample_interval)
		{
			balls[i - 1].direction = 1;
			balls[i - 1].index_in_path = balls[i].index_in_path + 2 * RADIUS / path.sample_interval;
			i--;
		}
		else //有一个小球不直接接触则停止向前速度的传递。
		{
			break;
		}
	}

	for (int i = 0; i < balls.size(); i++)  //小球根据direction更新位置。
	{
		balls[i].move_to_index_in_path(path);
		balls[i].change_index_by_direction(path);
	}

	Sleep(50);
}


void update_with_input()
{
	if (game_status != 0)
	{
		return;
	}

	MOUSEMSG m;
	while (MouseHit())
	{
		m = GetMouseMsg();
		if (m.uMsg == WM_MOUSEMOVE)  // 鼠标移动时			
		{
			cannon.update_with_mouse_move(m.x, m.y); //炮台旋转时将小球移动到对应位置上。
		}
		else if (m.uMsg == WM_RBUTTONDOWN)  //鼠标右键点击时，更改炮台要发射的小球的颜色。	
		{
			cannon.update_with_right_button_down();
		}
		else if (m.uMsg == WM_LBUTTONDOWN)
		{
			cannon.update_with_mouse_move(m.x, m.y);
			float speed_x = (cannon.ball.center.x - cannon.x) / 5;
			float speed_y = (cannon.ball.center.y - cannon.y) / 5;
			int is_collider = 0;

			while (is_collider == 0 && cannon.ball.center.y > 0 && cannon.ball.center.y < HEIGHT && cannon.ball.center.x>0 && cannon.ball.center.x < WIDTH) // 炮台小球超出边界就不用处理了
			{
				cannon.ball.center.x += speed_x; // 更新发射小球的位置
				cannon.ball.center.y += speed_y;
				show();

				for (int i = 0; i < balls.size(); i++)
				{
					float distance = Distance(balls[i].center.x, balls[i].center.y, m.x, m.y);
					if (distance < RADIUS)
					{
						is_collider = 1;
						cannon.update_with_mouse_move(m.x, m.y);

						Ball fire_ball = balls[i];
						fire_ball.color_Id = cannon.ball.color_Id;
						balls.insert(balls.begin() + i, fire_ball);

						int count = erase_same_color_balls(i, fire_ball, path, balls);

						if (count >= 3)
						{
							play_music_once(_T("coin.mp3"));
						}

						if (count == 0)
						{
							for (int j = i; j >= 0; j--)
							{
								if (balls[j].index_in_path - balls[j + 1].index_in_path <= 0)
								{
									balls[j].index_in_path = balls[j + 1].index_in_path + 2 * RADIUS / path.sample_interval;
								}
								else
								{
									break;
								}
							}
						}
						return;
					}
				}
			}
		}
	}
}


float Distance(float x1, float y1, float x2, float y2)
{
	float xd = x1 - x2;
	float yd = y1 - y2;
	float length = sqrt(xd * xd + yd * yd);
	return length;
}


void sleep(DWORD ms)
{
	static DWORD oldtime = GetTickCount();
	while (GetTickCount() - oldtime < ms)
		Sleep(1);
	oldtime = GetTickCount();
}


void play_music_once(TCHAR file_name[80])
{
	TCHAR cmdString1[50];
	swprintf_s(cmdString1, _T("open %s alias tmpmusic"), file_name);
	mciSendString(_T("close tmpmusic"), NULL, 0, NULL);
	mciSendString(cmdString1, NULL, 0, NULL);
	mciSendString(_T("play tmpmusic"), NULL, 0, NULL);
}


int erase_same_color_balls(int i, Ball fire_ball, Path& path, vector <Ball>& balls)
{
	//记录前后和插入的小球颜色一样的序号以得到对应的要删除的序号。
	vector<int> same_color_indexes;
	int forward = i;
	int backward = i;
	same_color_indexes.push_back(i);

	while (forward > 0 && balls[forward].color_Id == fire_ball.color_Id)
	{
		same_color_indexes.push_back(forward);

		//前面一个球和这个球间距过大则跳出循环。
		if (balls[forward - 1].index_in_path - balls[forward].index_in_path > 2 * RADIUS / path.sample_interval)
		{
			break;
		}
		forward--;
	}
	if (forward == 0 && balls[0].color_Id == fire_ball.color_Id)
	{
		same_color_indexes.push_back(forward);
	}

	while (backward < balls.size() - 1 && balls[backward].color_Id == fire_ball.color_Id)
	{
		same_color_indexes.push_back(backward);

		// 前面一个球和这个球间距过大则跳出循环。
		if (balls[backward].index_in_path - balls[backward + 1].index_in_path > 2 * RADIUS / path.sample_interval)
		{
			break;
		}
		backward++;
	}
	if (backward == balls.size() - 1 && balls[balls.size() - 1].color_Id == fire_ball.color_Id)
	{
		same_color_indexes.push_back(backward);
	}

	//去除同样颜色小球中重复的序号。
	sort(same_color_indexes.begin(), same_color_indexes.end());
	vector<int>::iterator ite = unique(same_color_indexes.begin(), same_color_indexes.end());
	same_color_indexes.erase(ite, same_color_indexes.end());

	int num_same_colors = same_color_indexes.size();

	//如果相同颜色的球达到三个或以上则把这些球给删除。
	if (num_same_colors >= 4)
	{
		int minIndex = same_color_indexes[0];
		int maxIndex = same_color_indexes[num_same_colors - 1];
		balls.erase(balls.begin() + minIndex, balls.begin() + maxIndex + 1);
		return num_same_colors;
	}
	return 0;
}


void game_over()
{
	balls.clear();
}
